/*
 * UAE - The Un*x Amiga Emulator
 *
 * Drive Click Emulation Support Functions
 *
 * Copyright 2004 James Bagg, Toni Wilen
 */

#include "sysconfig.h"
#include "driveclick.h"
#include "options.h"
#include "audio.h"
#include "sound.h"
#include "uae.h"
#include "zfile.h"
#include "win32gui.h"

#ifdef DRIVESOUND

namespace Floppy
{
    CArray<CString>Sound::floppy_sounds_names;
    Sound::FloppySamples Sound::Floppies[4];
    short* Sound::clickbuffer = nullptr;
    int Sound::clickcnt = 0;

    void Sound::FloppySample::DecodeWav(byte* s)
    {
        byte* src = s;

        if (memcmp(s, "RIFF", 4))
            return;
        if (memcmp(s + 8, "WAVE", 4))
            return;

        s += 12;
        int sl = len;
        while (s < src + sl)
        {
            if (!memcmp(s, "fmt ", 4))
                freq = s[8 + 4] | (s[8 + 5] << 8);
            if (!memcmp(s, "data", 4))
            {
                s += 4;
                sl = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);
                ASSERT(p == nullptr);
                p = new short[sl / 2];
                memcpy(p, s + 4, sl);
                len = sl / 2;
                return;
            }
            s += 8 + (s[4] | (s[5] << 8) | (s[6] << 16) | (s[7] << 24));
        }
    }

    int Sound::FloppySample::LoadSample(byte* buf)
    {
        if (buf == nullptr)
            return 0;

        DecodeWav(buf);
        free(buf);
        return 1;
    }

    void Sound::FloppySample::ProcessClicks()
    {
        int nClick = 0;

        for (int n = 0; n < CLICK_TRACKS; n++)
        {
            indexes[n] = 0;
            lengths[n] = 0;
        }
        for (int n = 0; n < len; n++)
        {
            short smp = p[n];
            if (smp > 0x6ff0 && nClick < CLICK_TRACKS)
            {
                indexes[nClick] = n - 128;
                lengths[nClick] = 2800;
                nClick++;
                n += 3000;
            }
        }
        if (nClick == 0)
        {
            for (int n = 0; n < CLICK_TRACKS; n++)
            {
                indexes[n] = 0;
                lengths[n] = len;
            }
        }
        else
        {
            if (nClick == 1)
            {
                lengths[0] = len - indexes[0];
                for (int n = 1; n < CLICK_TRACKS; n++)
                {
                    indexes[n] = indexes[0];
                    lengths[n] = lengths[0];
                }
            }
            else
            {
                for (int n = nClick; n < CLICK_TRACKS; n++)
                {
                    indexes[n] = indexes[nClick - 1];
                    lengths[n] = lengths[nClick - 1];
                }
            }
        }
    }

    void Sound::Clear()
    {
        for (int i = 0; i < 4; i++)
            Floppies[i].Clear();

        Helpers::safe_delete(&clickbuffer);
    }

    void Sound::Init()
    {
        Helpers::safe_delete(&clickbuffer);

        Clear();
        for (int i = 0; i < 4; i++)
        {
            FloppySlot* fs = &g_curr_conf.floppies[i];
            for (int j = 0; j < CLICK_TRACKS; j++)
            {
                Floppies[i].Samples[DS_CLICK].indexes[j] = 0;
                Floppies[i].Samples[DS_CLICK].lengths[j] = 0;
            }
            if (fs->floppy_sounds != "")
            {
                Floppies[i].wave_initialized = true;

                if (clickbuffer == nullptr)
                    clickbuffer = xmalloc(short, paula_sndbufsize / 2);

                for (int j = 0; j < CLICK_TRACKS; j++)
                    Floppies[i].Samples[DS_CLICK].lengths[j] = Floppies[i].Samples[DS_CLICK].len;

                int v = Floppies[i].Samples[DS_CLICK].LoadSample(
                    CLR::FloppiesSounds::LoadClick(fs->floppy_sounds,
                        &Floppies[i].Samples[DS_CLICK].len));
                if (v)
                    Floppies[i].Samples[DS_CLICK].ProcessClicks();

                v += Floppies[i].Samples[DS_SPIN].LoadSample(
                    CLR::FloppiesSounds::LoadSpin(fs->floppy_sounds,
                        &Floppies[i].Samples[DS_SPIN].len));

                v += Floppies[i].Samples[DS_SPINND].LoadSample(
                    CLR::FloppiesSounds::LoadSpinNoDisk(fs->floppy_sounds,
                        &Floppies[i].Samples[DS_SPINND].len));

                v += Floppies[i].Samples[DS_START].LoadSample(
                    CLR::FloppiesSounds::LoadStartup(fs->floppy_sounds,
                        &Floppies[i].Samples[DS_START].len));

                v += Floppies[i].Samples[DS_SNATCH].LoadSample(
                    CLR::FloppiesSounds::LoadSnatch(fs->floppy_sounds,
                        &Floppies[i].Samples[DS_SNATCH].len));

                for (int j = 0; j < DS_END; j++)
                    Floppies[i].Samples[j].len <<= DS_SHIFT;
                Floppies[i].Samples[DS_CLICK].pos = Floppies[i].Samples[DS_CLICK].len;
                Floppies[i].Samples[DS_SNATCH].pos = Floppies[i].Samples[DS_SNATCH].len;
            }
        }
    }

    int Sound::FloppySample::GetStep()
    {
        return (freq << DS_SHIFT) / ::Sound::SOUND_FREQ;
    }

    short Sound::getsample()
    {
        int smp = 0;
        int div = 0;

        for (int i = 0; i < 4; i++)
        {
            if (Floppies[i].wave_initialized)
            {
                FloppySample* ds_start = &Floppies[i].Samples[DS_START];
                FloppySample* ds_spin = Floppies[i].drv_has_disk ? &Floppies[i].Samples[DS_SPIN] :
                    &Floppies[i].Samples[DS_SPINND];
                FloppySample* ds_click = &Floppies[i].Samples[DS_CLICK];
                FloppySample* ds_snatch = &Floppies[i].Samples[DS_SNATCH];
                div += 2;
                if (Floppies[i].drv_spinning || Floppies[i].drv_starting)
                {
                    if (Floppies[i].drv_starting && Floppies[i].drv_has_spun)
                    {
                        if (ds_start->p && ds_start->pos < ds_start->len)
                        {
                            smp = ds_start->p[ds_start->pos >> DS_SHIFT];
                            ds_start->pos += ds_start->GetStep();
                        }
                        else
                        {
                            Floppies[i].drv_starting = false;
                        }
                    }
                    else if (Floppies[i].drv_starting && !Floppies[i].drv_has_spun)
                    {
                        if (ds_snatch->p && ds_snatch->pos < ds_snatch->len)
                        {
                            smp = ds_snatch->p[ds_snatch->pos >> DS_SHIFT];
                            ds_snatch->pos += ds_snatch->GetStep();
                        }
                        else
                        {
                            Floppies[i].drv_starting = false;
                            ds_start->pos = ds_start->len;
                            Floppies[i].drv_has_spun = true;
                        }
                    }
                    if (ds_spin->p && !Floppies[i].drv_starting)
                    {
                        if (ds_spin->pos >= ds_spin->len)
                            ds_spin->pos -= ds_spin->len;
                        smp = ds_spin->p[ds_spin->pos >> DS_SHIFT];
                        ds_spin->pos += ds_spin->GetStep();
                    }
                }
                if (ds_click->p && ds_click->pos < ds_click->len)
                {
                    smp += ds_click->p[ds_click->pos >> DS_SHIFT];
                    ds_click->pos += ds_click->GetStep();
                }
            }
        }

        if (div == 0)
            return 0;

        return smp / div;
    }

    short Sound::limit(int v)
    {
        if (v < -32768)
            v = -32768;
        if (v > 32767)
            v = 32767;
        return v;
    }

    void Sound::mix()
    {
        int total = ((byte*)paula_sndbufpt - (byte*)paula_sndbuffer) / 4;

        while (clickcnt < total)
        {
            clickbuffer[clickcnt] = getsample() * (100 - g_curr_conf.floppy_sound_volume) / 100;
            clickcnt++;
        }
    }

    void Sound::mix(short* sndbuffer, int size)
    {
        if (clickbuffer == nullptr)
            return;

        mix();
        clickcnt = 0;

        for (int i = 0; i < size / 2; i++)
        {
            short s = clickbuffer[i];

            sndbuffer[0] = limit(((sndbuffer[0] + s) * 2) / 3);
            sndbuffer[1] = limit(((sndbuffer[1] + s) * 2) / 3);

            sndbuffer += 2;
        }
    }

    void Sound::click(int drive, int cyl)
    {
        if (!Floppies[drive].wave_initialized)
            return;

        if (Floppies[drive].prevcyl == 0 && cyl == 0) // "noclick" check
            return;
        Floppies[drive].prevcyl = cyl;

        mix();

        Floppies[drive].Samples[DS_CLICK].pos = Floppies[drive].Samples[DS_CLICK].indexes[cyl] << DS_SHIFT;
        Floppies[drive].Samples[DS_CLICK].len = (Floppies[drive].Samples[DS_CLICK].indexes[cyl] +
            (Floppies[drive].Samples[DS_CLICK].lengths[cyl] / 2)) << DS_SHIFT;
    }

    void Sound::motor(int drive, int running)
    {
        if (!Floppies[drive].wave_initialized)
            return;

        mix();

        if (running == 0)
        {
            Floppies[drive].drv_starting = false;
            Floppies[drive].drv_spinning = false;
        }
        else
        {
            if (!Floppies[drive].drv_spinning)
            {
                Floppies[drive].drv_starting = true;
                Floppies[drive].drv_spinning = true;
                if (Floppies[drive].drv_has_disk && !Floppies[drive].drv_has_spun &&
                    Floppies[drive].Samples[DS_SNATCH].pos >= Floppies[drive].Samples[DS_SNATCH].len)
                    Floppies[drive].Samples[DS_SNATCH].pos = 0;
                if (running == 2)
                    Floppies[drive].Samples[DS_START].pos = 0;
                Floppies[drive].Samples[DS_SPIN].pos = 0;
            }
        }
    }

    void Sound::insert(int drive, bool eject)
    {
        if (!Floppies[drive].wave_initialized)
            return;

        if (eject)
            Floppies[drive].drv_has_spun = false;

        Floppies[drive].drv_has_disk = !eject;
    }

    void Sound::check_prefs()
    {
        if (!config_changed)
            return;
        if (g_curr_conf.floppy_sound_volume != g_changed_conf.floppy_sound_volume ||
            _tcscmp(g_curr_conf.floppies[0].floppy_sounds, g_changed_conf.floppies[0].floppy_sounds) ||
            _tcscmp(g_curr_conf.floppies[1].floppy_sounds, g_changed_conf.floppies[1].floppy_sounds) ||
            _tcscmp(g_curr_conf.floppies[2].floppy_sounds, g_changed_conf.floppies[2].floppy_sounds) ||
            _tcscmp(g_curr_conf.floppies[3].floppy_sounds, g_changed_conf.floppies[3].floppy_sounds))
        {
            g_curr_conf.floppy_sound_volume = g_changed_conf.floppy_sound_volume;
            for (int i = 0; i < 4; i++)
                g_curr_conf.floppies[i].floppy_sounds = g_changed_conf.floppies[i].floppy_sounds;
            Init();
        }
    }

    void Sound::load_floppy_sounds_names()
    {
        CLR::FloppiesSounds::GetSoundsSamplesList(&floppy_sounds_names);

        TCHAR txt[1024];
        WIN32GUI_LoadUIString(IDS_DRIVESOUND_NONE, txt, sizeof(txt) / sizeof(TCHAR));
        floppy_sounds_names.InsertAt(0, txt);
    }
}

#endif